/**
 * 
 */
package com.module.bp.bus.graph;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.dalonedrau.jogl.engine.GameApplet;
import com.dalonedrau.jogl.engine.JOGLErrorHandler;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.xmlparser.XMLParserUtility;
import com.module.bp.bus.BPGlobals;

/**
 * @author Donald
 */
public class GraphLoader {
	/** the one and only instance of the <code>GraphLoader</code> class. */
	private static GraphLoader	instance;
	/**
	 * Gives access to the singleton instance of {@link GraphLoader}.
	 * @return {@link GraphLoader}
	 */
	public static GraphLoader getInstance() {
		if (GraphLoader.instance == null) {
			GraphLoader.instance = new GraphLoader();
		}
		return GraphLoader.instance;
	}
	/**
	 * Connects all map hexes.
	 * @param g the {@link SimpleGraph} being connected
	 */
	private void connectMapHexes(final SimpleGraph g) {
		for (int row = 1; row < 24; row++) {
			for (int col = 1; col < 21; col++) {
				int vertex0 = col - 1 + 20 * (row - 1);
				// got hex col,row
				// there are 6 possible edges
				// start at TOP-LEFT
				int x = col - 1;
				int y = row;
				if (col % 2 != 0) {
					y--;
				}
				int vertex1 = x - 1 + 20 * (y - 1);
				if (vertex1 >= 0
						&& vertex1 < 460
						&& col % 20 != 1) {
					g.addWeightedEdge(vertex0, vertex1, 1f, false);
				}
				// TOP
				x = col;
				y = row - 1;
				vertex1 = x - 1 + 20 * (y - 1);
				if (vertex1 >= 0
						&& vertex1 < 460) {
					g.addWeightedEdge(vertex0, vertex1, 1f, false);
				}
				// TOP-RIGHT
				x = col + 1;
				y = row;
				if (col % 2 != 0) {
					y--;
				}
				vertex1 = x - 1 + 20 * (y - 1);
				if (vertex1 >= 0
						&& vertex1 < 460
						&& col % 20 != 0) {
					g.addWeightedEdge(vertex0, vertex1, 1f, false);
				}

				// BOTTOM-RIGHT
				x = col + 1;
				y = row;
				if (col % 2 == 0) {
					y++;
				}
				vertex1 = x - 1 + 20 * (y - 1);
				if (vertex1 >= 0
						&& vertex1 < 460
						&& col % 20 != 0) {
					g.addWeightedEdge(vertex0, vertex1, 1f, false);
				}

				// BOTTOM
				x = col;
				y = row + 1;
				vertex1 = x - 1 + 20 * (y - 1);
				if (vertex1 >= 0
						&& vertex1 < 460) {
					g.addWeightedEdge(vertex0, vertex1, 1f, false);
				}

				// BOTTOM-LEFT
				x = col - 1;
				y = row;
				if (col % 2 == 0) {
					y++;
				}
				vertex1 = x - 1 + 20 * (y - 1);
				if (vertex1 >= 0
						&& vertex1 < 460
						&& col % 20 != 1) {
					g.addWeightedEdge(vertex0, vertex1, 1f, false);
				}
			}
		}
	}
	public SimpleGraph loadOverworldGraph() {
		// vertices 0 - 459 are map hexes
		// vertices 460 - 498 are the Tragoth River
		// vertices 499 - 538 are the Nesser River
		// vertices 539 - 582 are the Greater Nesser River
		SimpleGraph g = new SimpleGraph(601);
		try {
			connectMapHexes(g);
			loadRiverTragoth(g);
			loadRiverNesser(g);
			loadRiverGreaterNesser(g);
			loadRiverDienstalBranch(g);
			loadRiverLargos(g);
			loadRoads(g);
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return g;
	}
	/**
	 * Loads all vertices and edges for the Dienstal Branch.
	 * @param g the {@link SimpleGraph} being populated
	 * @throws Exception if an error occurs
	 */
	private void loadRiverDienstalBranch(final SimpleGraph g)
			throws Exception {
		// remove hex connections that now go across river
		g.removeEdge(new WeightedEdge(333, 353, 1));
		g.removeEdge(new WeightedEdge(333, 354, 1));
		g.removeEdge(new WeightedEdge(334, 354, 1));
		g.removeEdge(new WeightedEdge(351, 371, 1));
		g.removeEdge(new WeightedEdge(351, 372, 1));
		g.removeEdge(new WeightedEdge(352, 353, 1));
		// create upstream connections
		for (int i = 583; i < 589; i++) {
			g.addWeightedEdge(i, i + 1, 0.375f, true);
		}
		// create downstream connections
		for (int i = 589; i > 583; i--) {
			g.addWeightedEdge(i, i - 1, 0.25f, true);
		}
		// create upstream connection to Greater Nesser
		g.addWeightedEdge(563, 583, 0.375f, true);
		g.addWeightedEdge(583, 562, 0.375f, true);
		// create downstream connection from Nesser
		g.addWeightedEdge(562, 583, 0.25f, true);
		g.addWeightedEdge(583, 563, 0.25f, true);
		// load hex connections from xml document
		loadXMLDoc(g, "com/module/bp/dat/xml/dienstal_branch.xml");
	}
	/**
	 * Loads all vertices and edges for the Greater Nesser River.
	 * @param g the {@link SimpleGraph} being populated
	 * @throws Exception if an error occurs
	 */
	private void loadRiverGreaterNesser(final SimpleGraph g)
			throws Exception {
		// remove hex connections that now go across river
		g.removeEdge(new WeightedEdge(166, 186, 1));
		g.removeEdge(new WeightedEdge(167, 186, 1));
		g.removeEdge(new WeightedEdge(186, 187, 1));
		g.removeEdge(new WeightedEdge(187, 206, 1));
		g.removeEdge(new WeightedEdge(206, 207, 1));
		g.removeEdge(new WeightedEdge(207, 226, 1));
		g.removeEdge(new WeightedEdge(207, 227, 1));
		g.removeEdge(new WeightedEdge(227, 228, 1));
		g.removeEdge(new WeightedEdge(228, 248, 1));
		g.removeEdge(new WeightedEdge(229, 248, 1));
		g.removeEdge(new WeightedEdge(248, 249, 1));
		g.removeEdge(new WeightedEdge(249, 268, 1));
		g.removeEdge(new WeightedEdge(268, 269, 1));
		g.removeEdge(new WeightedEdge(269, 288, 1));
		g.removeEdge(new WeightedEdge(288, 289, 1));
		g.removeEdge(new WeightedEdge(289, 308, 1));
		g.removeEdge(new WeightedEdge(308, 309, 1));
		g.removeEdge(new WeightedEdge(309, 328, 1));
		g.removeEdge(new WeightedEdge(309, 329, 1));
		g.removeEdge(new WeightedEdge(329, 330, 1));
		g.removeEdge(new WeightedEdge(329, 350, 1));
		g.removeEdge(new WeightedEdge(349, 350, 1));
		g.removeEdge(new WeightedEdge(350, 370, 1));
		g.removeEdge(new WeightedEdge(351, 370, 1));
		g.removeEdge(new WeightedEdge(370, 371, 1));
		g.removeEdge(new WeightedEdge(371, 390, 1));
		g.removeEdge(new WeightedEdge(371, 391, 1));
		g.removeEdge(new WeightedEdge(391, 392, 1));
		g.removeEdge(new WeightedEdge(392, 412, 1));
		g.removeEdge(new WeightedEdge(393, 412, 1));
		g.removeEdge(new WeightedEdge(393, 413, 1));
		g.removeEdge(new WeightedEdge(413, 414, 1));
		g.removeEdge(new WeightedEdge(413, 434, 1));
		g.removeEdge(new WeightedEdge(433, 434, 1));
		g.removeEdge(new WeightedEdge(433, 454, 1));
		g.removeEdge(new WeightedEdge(434, 454, 1));
		g.removeEdge(new WeightedEdge(435, 454, 1));
		g.removeEdge(new WeightedEdge(435, 455, 1));
		g.removeEdge(new WeightedEdge(435, 456, 1));
		g.removeEdge(new WeightedEdge(436, 456, 1));
		g.removeEdge(new WeightedEdge(437, 456, 1));
		g.removeEdge(new WeightedEdge(437, 457, 1));
		g.removeEdge(new WeightedEdge(453, 454, 1));
		g.removeEdge(new WeightedEdge(457, 458, 1));
		// create upstream connections
		for (int i = 574; i > 539; i--) {
			g.addWeightedEdge(i, i - 1, 0.375f, true);
		}
		for (int i = 582; i > 575; i--) {
			g.addWeightedEdge(i, i - 1, 0.375f, true);
		}
		g.addWeightedEdge(573, 575, 0.375f, true);
		g.addWeightedEdge(575, 572, 0.375f, true);
		// create downstream connections
		for (int i = 539; i < 574; i++) {
			g.addWeightedEdge(i, i + 1, 0.25f, true);
		}
		for (int i = 575; i < 582; i++) {
			g.addWeightedEdge(i, i + 1, 0.25f, true);
		}
		g.addWeightedEdge(572, 575, 0.25f, true);
		g.addWeightedEdge(575, 573, 0.25f, true);
		// create upstream connection to Nesser
		g.addWeightedEdge(523, 539, 0.375f, true);
		g.addWeightedEdge(539, 522, 0.375f, true);
		// create downstream connection from Nesser
		g.addWeightedEdge(522, 539, 0.25f, true);
		g.addWeightedEdge(539, 523, 0.25f, true);
		// load hex connections from xml document
		loadXMLDoc(g, "com/module/bp/dat/xml/greater_nesser.xml");
	}
	/**
	 * Loads all vertices and edges for the Largos River.
	 * @param g the {@link SimpleGraph} being populated
	 * @throws Exception if an error occurs
	 */
	private void loadRiverLargos(final SimpleGraph g)
			throws Exception {
		// remove hex connections that now go across river
		g.removeEdge(new WeightedEdge(219, 239, 1));
		g.removeEdge(new WeightedEdge(238, 239, 1));
		g.removeEdge(new WeightedEdge(239, 258, 1));
		g.removeEdge(new WeightedEdge(258, 259, 1));
		g.removeEdge(new WeightedEdge(258, 278, 1));
		g.removeEdge(new WeightedEdge(257, 278, 1));
		g.removeEdge(new WeightedEdge(277, 278, 1));
		g.removeEdge(new WeightedEdge(277, 297, 1));
		g.removeEdge(new WeightedEdge(277, 298, 1));
		g.removeEdge(new WeightedEdge(296, 297, 1));
		g.removeEdge(new WeightedEdge(316, 297, 1));
		// create upstream connections
		for (int i = 600; i > 590; i--) {
			g.addWeightedEdge(i, i - 1, 0.375f, true);
		}
		// create downstream connections
		for (int i = 590; i < 600; i++) {
			g.addWeightedEdge(i, i + 1, 0.25f, true);
		}
		// load hex connections from xml document
		loadXMLDoc(g, "com/module/bp/dat/xml/largos.xml");
	}
	/**
	 * Loads all vertices and edges for the Nesser River.
	 * @param g the {@link SimpleGraph} being populated
	 * @throws Exception if an error occurs
	 */
	private void loadRiverNesser(final SimpleGraph g) throws Exception {
		// remove hex connections that now go across river
		g.removeEdge(new WeightedEdge(13, 33, 1));
		g.removeEdge(new WeightedEdge(13, 34, 1));
		g.removeEdge(new WeightedEdge(31, 51, 1));
		g.removeEdge(new WeightedEdge(31, 52, 1));
		g.removeEdge(new WeightedEdge(32, 33, 1));
		g.removeEdge(new WeightedEdge(32, 52, 1));
		g.removeEdge(new WeightedEdge(49, 70, 1));
		g.removeEdge(new WeightedEdge(50, 51, 1));
		g.removeEdge(new WeightedEdge(50, 70, 1));
		g.removeEdge(new WeightedEdge(69, 70, 1));
		g.removeEdge(new WeightedEdge(69, 90, 1));
		g.removeEdge(new WeightedEdge(89, 90, 1));
		g.removeEdge(new WeightedEdge(89, 109, 1));
		g.removeEdge(new WeightedEdge(89, 110, 1));
		g.removeEdge(new WeightedEdge(107, 127, 1));
		g.removeEdge(new WeightedEdge(107, 128, 1));
		g.removeEdge(new WeightedEdge(108, 109, 1));
		g.removeEdge(new WeightedEdge(108, 128, 1));
		g.removeEdge(new WeightedEdge(125, 146, 1));
		g.removeEdge(new WeightedEdge(126, 127, 1));
		g.removeEdge(new WeightedEdge(126, 146, 1));
		g.removeEdge(new WeightedEdge(145, 146, 1));
		g.removeEdge(new WeightedEdge(145, 166, 1));
		g.removeEdge(new WeightedEdge(165, 166, 1));
		g.removeEdge(new WeightedEdge(165, 185, 1));
		g.removeEdge(new WeightedEdge(165, 186, 1));
		g.removeEdge(new WeightedEdge(183, 203, 1));
		g.removeEdge(new WeightedEdge(183, 204, 1));
		g.removeEdge(new WeightedEdge(184, 185, 1));
		g.removeEdge(new WeightedEdge(184, 204, 1));
		g.removeEdge(new WeightedEdge(202, 203, 1));
		g.removeEdge(new WeightedEdge(221, 241, 1));
		g.removeEdge(new WeightedEdge(221, 242, 1));
		g.removeEdge(new WeightedEdge(222, 203, 1));
		g.removeEdge(new WeightedEdge(222, 223, 1));
		g.removeEdge(new WeightedEdge(222, 242, 1));
		g.removeEdge(new WeightedEdge(240, 241, 1));
		g.removeEdge(new WeightedEdge(260, 241, 1));
		g.removeEdge(new WeightedEdge(260, 261, 1));
		g.removeEdge(new WeightedEdge(260, 280, 1));
		// create upstream connections
		for (int i = 538; i > 499; i--) {
			g.addWeightedEdge(i, i - 1, 0.375f, true);
		}
		// create downstream connections
		for (int i = 499; i < 538; i++) {
			g.addWeightedEdge(i, i + 1, 0.25f, true);
		}
		// create upstream connection to Tragoth
		g.addWeightedEdge(499, 488, 0.375f, true);
		// create downstream connection from Tragoth
		g.addWeightedEdge(488, 499, 0.25f, true);
		// load hex connections from xml document
		loadXMLDoc(g, "com/module/bp/dat/xml/nesser.xml");
	}
	/**
	 * Loads all vertices and edges for the Tragoth River.
	 * @param g the {@link SimpleGraph} being populated
	 * @throws Exception if an error occurs
	 */
	private void loadRiverTragoth(final SimpleGraph g) throws Exception {
		// remove hex connections that now go across river
		g.removeEdge(new WeightedEdge(0, 20, 1));
		g.removeEdge(new WeightedEdge(1, 20, 1));
		g.removeEdge(new WeightedEdge(1, 21, 1));
		g.removeEdge(new WeightedEdge(3, 23, 1));
		g.removeEdge(new WeightedEdge(5, 25, 1));
		g.removeEdge(new WeightedEdge(5, 26, 1));
		g.removeEdge(new WeightedEdge(6, 26, 1));
		g.removeEdge(new WeightedEdge(7, 26, 1));
		g.removeEdge(new WeightedEdge(7, 27, 1));
		g.removeEdge(new WeightedEdge(7, 28, 1));
		g.removeEdge(new WeightedEdge(8, 28, 1));
		g.removeEdge(new WeightedEdge(9, 28, 1));
		g.removeEdge(new WeightedEdge(9, 29, 1));
		g.removeEdge(new WeightedEdge(9, 30, 1));
		g.removeEdge(new WeightedEdge(10, 30, 1));
		g.removeEdge(new WeightedEdge(11, 30, 1));
		g.removeEdge(new WeightedEdge(11, 31, 1));
		g.removeEdge(new WeightedEdge(11, 32, 1));
		g.removeEdge(new WeightedEdge(12, 32, 1));
		g.removeEdge(new WeightedEdge(12, 13, 1));
		g.removeEdge(new WeightedEdge(13, 14, 1));
		g.removeEdge(new WeightedEdge(13, 33, 1));
		g.removeEdge(new WeightedEdge(13, 34, 1));
		g.removeEdge(new WeightedEdge(14, 34, 1));
		g.removeEdge(new WeightedEdge(15, 34, 1));
		g.removeEdge(new WeightedEdge(15, 35, 1));
		g.removeEdge(new WeightedEdge(15, 36, 1));
		g.removeEdge(new WeightedEdge(16, 36, 1));
		g.removeEdge(new WeightedEdge(17, 36, 1));
		g.removeEdge(new WeightedEdge(17, 37, 1));
		g.removeEdge(new WeightedEdge(17, 38, 1));
		g.removeEdge(new WeightedEdge(18, 38, 1));
		g.removeEdge(new WeightedEdge(19, 38, 1));
		g.removeEdge(new WeightedEdge(19, 39, 1));
		g.removeEdge(new WeightedEdge(21, 22, 1));
		g.removeEdge(new WeightedEdge(22, 23, 1));
		g.removeEdge(new WeightedEdge(22, 42, 1));
		g.removeEdge(new WeightedEdge(23, 24, 1));
		g.removeEdge(new WeightedEdge(24, 25, 1));
		g.removeEdge(new WeightedEdge(24, 44, 1));
		// create upstream connections
		for (int i = 460; i < 498; i++) {
			g.addWeightedEdge(i, i + 1, 0.375f, true);
		}
		// create downstream connections
		for (int i = 498; i > 460; i--) {
			g.addWeightedEdge(i, i - 1, 0.25f, true);
		}
		// load hex connections from xml document
		loadXMLDoc(g, "com/module/bp/dat/xml/tragoth.xml");
	}
	/**
	 * Loads all edges connected by roads.
	 * @param g the {@link SimpleGraph} being populated
	 * @throws Exception if an error occurs
	 */
	private void loadRoads(final SimpleGraph g) throws Exception {
		// load hex connections from xml document
		loadXMLRoadDoc(g, "com/module/bp/dat/xml/roads.xml");
	}
	/**
	 * Loads an xml document and processes it.
	 * @param graph the graph used for the document
	 * @param fileName the name of the xml file
	 * @throws Exception if an error occurs
	 */
	private void loadXMLDoc(final SimpleGraph graph,
			final String fileName) throws Exception {
		InputStream is = null;
		if (fileName != null
				&& fileName.startsWith("com")) { // jar implementation
			is = GraphLoader.class.getClassLoader().getResourceAsStream(
							fileName);
		} else { // file implementation
			File file = new File(".\\", fileName);
			// all work is done if the data file exists.
			// otherwise, an error dialog is displayed
			if (file.exists()) {
				is = new FileInputStream(file);
			} else {
				PooledStringBuilder sb =
						StringBuilderPool.getInstance().getStringBuilder();
				sb.append("Init loadBPMap() - file ");
				sb.append(fileName.toString());
				sb.append(" does not exist in library folder.");
				Exception ex = new Exception(sb.toString());
				sb.returnToPool();
				throw ex;
			}
			file = null;
		}
		Document serviceDoc = XMLParserUtility.getInstance().parseXmlFile(is);
		parseOverworldEdges(serviceDoc, graph);
	}
	/**
	 * Loads an xml document and processes it.
	 * @param graph the graph used for the document
	 * @param fileName the name of the xml file
	 * @throws Exception if an error occurs
	 */
	private void loadXMLRoadDoc(final SimpleGraph graph,
			final String fileName) throws Exception {
		InputStream is = null;
		if (fileName != null
				&& fileName.startsWith("com")) { // jar implementation
			is = GraphLoader.class.getClassLoader().getResourceAsStream(
							fileName);
		} else { // file implementation
			File file = new File(".\\", fileName);
			// all work is done if the data file exists.
			// otherwise, an error dialog is displayed
			if (file.exists()) {
				is = new FileInputStream(file);
			} else {
				PooledStringBuilder sb =
						StringBuilderPool.getInstance().getStringBuilder();
				sb.append("Init loadBPMap() - file ");
				sb.append(fileName.toString());
				sb.append(" does not exist in library folder.");
				Exception ex = new Exception(sb.toString());
				sb.returnToPool();
				throw ex;
			}
			file = null;
		}
		Document serviceDoc = XMLParserUtility.getInstance().parseXmlFile(is);
		parseOverworldRoadEdges(serviceDoc, graph);
	}
	/**
	 * Parses the edges from an xml document for the overworld map.
	 * @param dom the xml document
	 * @param graph the graph being populated
	 * @throws Exception if an error occurs
	 */
	private void parseOverworldEdges(final Document dom,
			final SimpleGraph graph) throws Exception {
		Element docEle = dom.getDocumentElement();
		NodeList nl = docEle.getElementsByTagName("edge");
		if (nl != null && nl.getLength() > 0) {
			for (int i = 0; i < nl.getLength(); i++) {
				Element element = (Element) nl.item(i);
				int from = XMLParserUtility.getInstance().getIntValue(element,
						"from");
				int to = XMLParserUtility.getInstance().getIntValue(element,
						"to");
				// edges are in xml doc going from hex to river
				// weight going from hex to river is always 0.125
				graph.addWeightedEdge(from, to,
						BPGlobals.GRAPH_LAND_TO_RIVER_WEIGHT, true);
				// reverse the edge and add connection from river to hex
				// weight going from river to hex is always flag value of 0.8375
				graph.addWeightedEdge(to, from, 0.8375f, true);
			}
		}
	}
	/**
	 * Parses the edges from an xml document for the overworld map.
	 * @param dom the xml document
	 * @param graph the graph being populated
	 * @throws Exception if an error occurs
	 */
	private void parseOverworldRoadEdges(final Document dom,
			final SimpleGraph graph) throws Exception {
		Element docEle = dom.getDocumentElement();
		NodeList nl = docEle.getElementsByTagName("edge");
		if (nl != null && nl.getLength() > 0) {
			for (int i = 0; i < nl.getLength(); i++) {
				Element element = (Element) nl.item(i);
				int from = XMLParserUtility.getInstance().getIntValue(element,
						"from");
				int to = XMLParserUtility.getInstance().getIntValue(element,
						"to");
				// edges are in xml doc going from road to road
				graph.addRoadEdge(from, to, 1f, false);
			}
		}
	}
}
